Reading Dynamic State
Beyond reading static, cached state from your process, HyperBEAM allows you to perform on-the-fly computations on that state using Lua. This guide explains how to create and use "transformation functions" to return dynamic, computed data without altering the underlying state of your process.
This is a powerful pattern for creating efficient data APIs for your applications, reducing client-side logic, and minimizing data transfer.
This guide assumes you are already familiar with exposing static process state.
How it Works: The Lua Device
The magic behind this is the lua@5.3a
device, which can execute a Lua script against a message. In this pattern, we use a HyperBEAM URL (hashpath) to construct a pipeline:
- First, we grab the latest state of an AO process.
- Then, we pipe that state as the
base
message into thelua@5.3a
device. - We tell the Lua device which script to load (from an Arweave transaction) and which function to execute.
- The function runs, processing the
base
state. - Finally, the result of the function is returned over HTTP.
Example: Calculating Circulating Supply
Let's consider a practical example: a token process where we have patched the Balances
table to be readable. Rather than forcing clients to download all balance data to compute the total supply, we can do it on the HyperBEAM node.
1. The Transformation Function
First, create a Lua script (sum.lua
) with a function that takes the state (base
) and calculates the sum of balances.
-- sum.lua
function sum(base, req)
-- Initialize total supply counter
local totalSupply = 0
local total = 0
-- Check if we have balances in our state
if base.balances then
-- Iterate through all balances and sum them
for address, balance in pairs(base.balances) do
-- Ensure balance is a number and add to total
local numBalance = tonumber(balance) or 0
totalSupply = totalSupply + numBalance
total = total + 1
end
end
-- Return the computed result as a table
return {
CirculatingSupply = tostring(math.floor(totalSupply)),
BalanceCount = tostring(math.floor(total))
}
end
The transformation function receives two arguments:
base
: The message being processed, which in our pipeline will be the cached state data from your process.req
: The incoming request object, which contains parameters and other metadata.
2. Publishing the Function
Next, publish your Lua script to Arweave. The arx
CLI tool is recommended for this.
# Install arx globally
npm i -g @permaweb/arx
# Upload your Lua function to Arweave
arx upload sum.lua \
-w PATH_TO_WALLET.json \
-t arweave \
--content-type application/lua \
--tags Data-Protocol ao
arx
will return a transaction ID for your script. Let's say it's LUA_SCRIPT_TX_ID
.
3. Calling the Function
With the process ID (YOUR_PROCESS_ID
) and the script transaction ID (LUA_SCRIPT_TX_ID
), you can construct a URL to call your function:
GET /<YOUR_PROCESS_ID>~process@1.0/now/~lua@5.3a&module={LUA_SCRIPT_TX_ID}/sum/serialize~json@1.0
This URL breaks down as follows:
/{YOUR_PROCESS_ID}~process@1.0
: Targets the AO process and its state./now
: Gets the most current state./~lua@5.3a&module={LUA_SCRIPT_TX_ID}
: This is the key part. It tells HyperBEAM to take the output of the previous step (the process state) and process it with thelua@5.3a
device, loading your script from themodule
transaction./sum
: Calls thesum
function within your Lua script./serialize~json@1.0
: Takes the table returned by your function and serializes it into a JSON object.
4. Integrating into an Application
Here's how you could fetch this dynamic data in a JavaScript application:
// Fetch circulating supply with JSON serialization
const processId = "FkJPkIHp_Gc_7KOLbtyzowPcJUc3SG_G25SJp0fbTmE"; // An example process
const moduleId = "QSBQZsowVRdvsEbdTv-KEF4_Z5bYf11M3X5-8LN0NM4"; // The example sum.lua script
const hyperbeam = "router-1.forward.computer";
async function getDynamicState() {
const url = `https://${hyperbeam}/${processId}~process@1.0/now/~lua@5.3a&module=${moduleId}/sum/serialize~json@1.0`;
const response = await fetch(url);
const data = await response.json();
console.log(`Total Supply: ${data.circulatingsupply}`);
console.log(`Token Holders: ${data.balancecount}`);
}
getDynamicState();
This approach significantly improves performance by offloading computation from the client to the HyperBEAM node and reducing the amount of data sent over the network.